﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenCompiler.CodeGen.Emit;
using OpenCompiler.CodeGen.Exceptions;
using OpenCompiler.CodeGen.Implementation;
using OpenCompiler.CodeGen.Tests.Interfaces;

namespace OpenCompiler.CodeGen.Tests.Specs_for_building_types_using_emit
{
	[TestClass]
	public class When_building_a_type_from_an_interface
	{
		private IConstructionContext _constructionContext;
		

		[TestInitialize]
		public void Setup()
		{
			this._constructionContext = new ConstructionContext();
			EmitManager.Instance.Reset();
		}
	
		[TestMethod]
		public void Type_built_without_name_causes_exception()
		{
			var typeBuilder = this._constructionContext.BuildFrom<ISimpleInterface>();

			try
			{
				var actualType = this._constructionContext.Construct(typeBuilder);
				Assert.Fail("Creation of unnamed types should not be allowed");
			} catch( UnnamedTypeException ) { }
		}

		private ITypeBuilder<T> BuildFromInterface<T>(string name) where T:class
		{
			var typeBuilder = this._constructionContext.BuildFrom<T>().WithName(name);
			return typeBuilder;
		}


		private Type BuildSimpleTestType()
		{
			var typeBuilder = this.BuildFromInterface<ISimpleInterface>("TestType");
			var actualType = this._constructionContext.Construct(typeBuilder);
			return actualType;			
		}

		private Type BuildSingleMethodType()
		{
			var typeBuilder = this.BuildFromInterface<ISingleMethodInterface>("TestType");
			var actualType = this._constructionContext.Construct(typeBuilder);
			return actualType;			
		}

		private Type BuildSingleMethodTypeAndAddMethod()
		{
			var typeBuilder = this.BuildFromInterface<ISingleMethodInterface>("TestType");
			var bodyBuilder = typeBuilder.Method(o => o.Method()).Body(MethodBodyContext.Body);
			
			bodyBuilder.Statement(o => Console.WriteLine("Hello world"));
			bodyBuilder.Statement(o => Console.WriteLine("And this is the second statement"));

			var actualType = this._constructionContext.Construct(typeBuilder);
			return actualType;
		}

		


		[TestMethod]
		public void Type_built_is_a_valid_type()
		{
			var actualType = this.BuildSimpleTestType();
			Assert.IsNotNull(actualType);
		}

		[TestMethod]
		public void Type_built_has_correct_name()
		{
			string typeName = "TestType";
			var actualType = this.BuildSimpleTestType();
			Assert.AreEqual(typeName, actualType.Name);
			
		}

		[TestMethod]
		public void Not_implementing_members_should_cause_exception()
		{
			try
			{
				var actualType = this.BuildSingleMethodType();
				Assert.Fail("Construction didn't cause an exception - it should throw an exception if methods aren't implemented for the interface");
			} catch( MethodNotImplementedException )
			{
				
			}

		}

		[TestMethod]
		public void Type_should_contain_method_when_method_is_added()
		{
			var actualType = this.BuildSingleMethodTypeAndAddMethod();

			var method = actualType.GetMethod("Method");
			Assert.IsNotNull(method);
		}

		[TestMethod]
		public void Type_is_an_inheritor_of_the_given_interface()
		{
			var actualType = this.BuildSimpleTestType();

			Assert.IsTrue(typeof(ISimpleInterface).IsAssignableFrom(actualType));
		}

		[TestMethod]
		public void Type_is_a_class()
		{
			var actualType = this.BuildSimpleTestType();
			Assert.IsTrue(actualType.IsClass);
		}
	}
}